﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.Text;
using System.IO;
using System.Web.Caching;
using System.Reflection;
using GoodStuff.Text;
using System.Configuration;

namespace GoodStuff.SharePoint2010.Providers
{
    /// <summary>
    /// A virtual path/file provider which allows you to run a native asp.Net webapplication with test pages containing webparts (and its usercontrols) 
    /// directly from your SharePoint solution (project).
    /// </summary>
    /// <remarks>
    /// To get the party started, add a global.asax to your test-webapplication, and register the VirtualSharepointProvider by passing the assemblyname 
    /// of your main SharePoint project into the constructor HostingEnvironment.RegisterVirtualPathProvider(new VirtualSharepointProvider(assemblyName))
    /// Assumption is that the test-webapplication folder has the same parentfolder as your main SharePoint project directory and that the foldername of 
    /// your project equals the assemblyname. You can override this behaviour by specifying the solution directory containing the projectfolder via 
    /// the appsetting "GoodStuff.SharePoint2010.Providers.VirtualSharePointProvider.SolutionDir" in your configuration file.
    /// </remarks>
    public class VirtualSharePointProvider : VirtualPathProvider
    {
        private const string __workingFolder_appSettingKey = "GoodStuff.SharePoint2010.Providers.VirtualSharePointProvider.SolutionDir";

        private string _workingFolder;
        private string _sharePointAssembly;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="assemblyName">The name of the assembly (assumption: it equals the name of the SharePoint project and project namespace</param>
        public VirtualSharePointProvider(string assemblyName)
        {
            //Determine working directory. Use current of explicit configured via AppSetting
            if (string.IsNullOrEmpty(ConfigurationManager.AppSettings[__workingFolder_appSettingKey]))
            {
                _workingFolder = System.IO.Path.GetDirectoryName(HostingEnvironment.ApplicationPhysicalPath);
            }
            else
            {
                _workingFolder = ConfigurationManager.AppSettings[__workingFolder_appSettingKey];
            }

            _workingFolder = System.IO.Path.GetDirectoryName(_workingFolder);
            _workingFolder = System.IO.Path.Combine(_workingFolder, assemblyName);

            _sharePointAssembly = assemblyName; //save assembly name
        }

        /// <summary>
        /// The Working folder the Provider operates for
        /// </summary>
        public string WorkingFolder { get { return _workingFolder; } }

        /// <summary>
        /// The Assembly name
        /// </summary>
        public string AssemblyName { get { return _sharePointAssembly; } }

        /// <summary>
        /// Checks whether the file is a SharePoint file (if it exists is regular VirtualPathProvider baselogic)
        /// </summary>
        /// <param name="virtualPath">path</param>
        /// <returns>file exists?</returns>
        public override bool FileExists(string virtualPath)
        {
            if (IsPathVirtual(virtualPath))
            {
                return true;
            }

            return base.FileExists(virtualPath);
        }

        /// <summary>
        /// Checks whether the directory is a SharePoint file (if it exists is regular VirtualPathProvider baselogic)
        /// </summary>
        /// <param name="virtualDir"></param>
        /// <returns>directory exists?</returns>
        public override bool DirectoryExists(string virtualDir)
        {
            if (IsPathVirtual(virtualDir))
            {
                return true;
            }
            return base.DirectoryExists(virtualDir);
        }

        /// <summary>
        /// Checks whether the file is a SharePoint file and if so returns it from the SharePoint project. 
        /// If not: VirtualPathProvider baselogic will return the file
        /// </summary>
        /// <param name="virtualPath"></param>
        /// <returns>The file </returns>
        public override VirtualFile GetFile(string virtualPath)
        {
            if (IsPathVirtual(virtualPath))
            {
                return new VirtualSharePointProviderFile(this, GetPhysicalFile(virtualPath), virtualPath, this.IsHostedInVirtualDirectory(virtualPath));
            }

            return base.GetFile(virtualPath);
        }

        /// <summary>
        /// Returns the path of the SharePoint file from the SharePoint project directory
        /// </summary>
        /// <param name="virtualPath">virtualPath to the file</param>
        /// <returns>the path of the file on disk</returns>
        private string GetPhysicalFile(string virtualPath)
        {
            //Example C:\Projects\Customer\Product\Product.SharePoint\Product.SharePoint\WebParts\HelloWorldWebPart\HelloWorldWebPartUserControl.ascx

            //To avoid issues with casing, we transform everything to lowercases.
            string realFile = virtualPath.Replace(this.AssemblyName + ".WebParts", this.AssemblyName + "/WebParts").ToLower();
            if (realFile.Contains("/webparts/"))
            {
                realFile = realFile.Replace("/_controltemplates/" + this.AssemblyName.ToLower() + "/", @"~/");
            }
            else
            {
                realFile = realFile.Replace("/_controltemplates/", @"~/Controltemplates/");
            }
            realFile = realFile.Replace("/_layouts/images/", @"~/Images/");
            realFile = realFile.Replace("/_layouts/", @"~/Layouts/");

            //it is either _layouts/Images -> ~/Images
            //or _layouts ~/Layouts

            realFile = realFile.Replace("/_layouts/", @"~/");

            //remove virtual directory when hosted in virtual directory
            realFile = RemoveVirtualDirectoryPrefix(realFile);

            return realFile;
        }

        /// <summary>
        /// Checks whether the file is a SharePoint file and if so returns the depencies with respect to the SharePoint project directory.
        /// If not: VirtualPathProvider baselogic will return the dependency
        /// </summary>
        /// <param name="virtualPath">virtualPath to the file</param>
        /// <param name="virtualPathDependencies">depencies</param>
        /// <param name="utcStart">date used for calling baselogic</param>
        /// <returns>CacheDependency</returns>
        public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            if (IsPathVirtual(virtualPath))
            {
                string realFile = GetPhysicalFile(virtualPath);
                realFile = realFile.Replace("~/", WorkingFolder + "/");
                realFile = RemoveVirtualDirectoryPrefix(realFile);
                return new CacheDependency(realFile);
            }

            return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
        }

        private bool IsHostedInVirtualDirectory(string path)
        {
            string virtualDir = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
            return !string.IsNullOrEmpty(virtualDir) && virtualDir != "/";
        }

        private string PrefixWithVirtualDirectoryWhenHostedInIIS(string path)
        {
            string virtualDir = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath; //Equals "/" when buildin webserver is used, /virtual dir when IIS is used.
            if (this.IsHostedInVirtualDirectory(path))
            {
                //Hosted within IIS, mappath is prefixed with virtual directory, so append the path check with that virtual directory.
                virtualDir = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath.ToUpper(); // Example: "/ISS.TESTWEB"

                return virtualDir + path;
            }
            else
            {
                return path;
            }
        }

        private string RemoveVirtualDirectoryPrefix(string path)
        {
            if (this.IsHostedInVirtualDirectory(path))
            {
                return path.ToLower().Replace(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath.ToLower(), string.Empty);
            }
            else
            {
                return path;
            }
        }

        private bool IsPathVirtual(string path)
        {
            string path_controlTemplates = "/_CONTROLTEMPLATES/";
            string path_layouts = "/_LAYOUTS/";

            path_controlTemplates = this.PrefixWithVirtualDirectoryWhenHostedInIIS(path_controlTemplates);
            path_layouts = this.PrefixWithVirtualDirectoryWhenHostedInIIS(path_layouts);

            if (path.ToUpper().StartsWith(path_controlTemplates) || path.ToUpper().StartsWith(path_layouts))
            {
                return true;
            }
            return false;
        }
    }

    /// <summary>
    /// Helperclass for SharePoint specific VirtualFile
    /// </summary>
    public class VirtualSharePointProviderFile : VirtualFile
    {
        private string _filename;
        private VirtualSharePointProvider _provider;
        bool _virtualDirectory = false;
        
        /// <summary>
        /// Constructor for a VirtualSharePointProviderFile
        /// </summary>
        /// <param name="provider">the calling provider</param>
        /// <param name="filename">file on disk</param>
        /// <param name="virtualFile">virtual filepath of the file</param>
        /// <param name="virtualDirectory">virtual folder/path of the file</param>
        public VirtualSharePointProviderFile(VirtualSharePointProvider provider, string filename, string virtualFile, bool virtualDirectory):base(virtualFile)
        {
            _filename = filename;
            _provider = provider;
            _virtualDirectory = virtualDirectory;
        }

        /// <summary>
        /// Override to open the stream to the SharePoint file
        /// </summary>
        /// <returns></returns>
        public override System.IO.Stream Open()
        {
            //project root.
            string realfile = _filename.Replace("~/", _provider.WorkingFolder + "/").ToLower();

            if (_virtualDirectory)
            {
                realfile = realfile.Replace(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath.ToLower(), string.Empty);
            }

            if (realfile.EndsWith(".ascx"))
            {
                string body = System.IO.File.ReadAllText(realfile);
                body = body.Replace("$SharePoint.Project.AssemblyFullName$", _provider.AssemblyName);

                byte[] byteArray = Encoding.ASCII.GetBytes(body);
                MemoryStream stream = new MemoryStream(byteArray);
                return stream;
            }
            else
            {
                return File.OpenRead(realfile);
            }
        }
    }
}